#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>

#include "tag.h"
#include "var.h"
#include "template.h"
#include "log.h"


static var_arr_t *vars = NULL;


void
init_var(void)
{
  vars = (var_arr_t *) array_create(sizeof(struct var *), 1);
  assert(vars);
  BUILTINS
  if (!vars->used) W2("No bultins available");
}

void
free_var(void)
{
  array_free(vars);
}

void
register_var(const var_t *vt)
{
  array_add_data(vars, (void *)&vt, 1);
}

static var_t *
var_foreach(size_t *idx_ptr)
{
  var_t **var_ptr = NULL;

  if (!idx_ptr) return (NULL);
  if (*idx_ptr >= vars->used) return (NULL);
  var_ptr = (var_t **) array_get_offset(vars, (*idx_ptr)++);
  return ( *(var_ptr) );
}

static void
check_var_args(const parse_data_t *pd, const tag_data_t *td, dict_t *attrs, const char **var_args)
{
  bool afound;
  size_t idx;
  const char *arg = NULL;
  const char *key = NULL;
  const char **va;
  dict_elem_t *e = NULL;

  if (lookup(attrs, TMPL_ATTR) != NULL) {
    if ( td->var_ptr->vflags & VAR_TMPLT_NONE ) {
      E("%s: no template allowed for %s", get_string(pd->file_name), td->var_ptr->vname);
    }
  } else if ( td->var_ptr->vflags & VAR_TMPLT_REQ ) {
    W("%s: missing template argument for %s, using default template", get_string(pd->file_name), td->var_ptr->vname);
  }

  for (idx = 1; idx < dict_len(attrs); idx++)
  {
    afound = false;
    if (!var_args)
    {
      E("%s: no attributes allowed for %s", get_string(pd->file_name), td->var_ptr->vname);
      break;
    }

    e = (dict_elem_t *) get_dict_elem(attrs, idx);
    if (!e) {
      E("%s: no dict element at %lu", get_string(pd->file_name), idx);
      continue;
    }
    key = e->key;
    if (strcmp(key, VAR_NAME) == 0) continue;
    if (strcmp(key, TMPL_ATTR) == 0) continue;
    va = var_args;
    while ( (arg = *(va)) )
    {
      va++;
      if (strcmp(arg, key) == 0) {
        afound = true;
        break;
      }
    }

    if (!afound) W("%s: unknown attribute %s for %s", get_string(pd->file_name), key, td->var_ptr->vname);
  }
}

void
check_var_pdata(const pdata_arr_t *pd_arr)
{
  bool var_found;
  size_t idx, idx2, idx3;
  const char *var_name = NULL;
  var_t **var = NULL;
  parse_data_t *pd = NULL;
  tag_data_t *td = NULL;
  dict_t *attrs = NULL;

  if (!pd_arr || !vars) return;
  for (idx = 0; idx < pd_arr->used; idx++)
  {
    pd = (parse_data_t *) array_get_offset((pdata_arr_t *)pd_arr, idx);
    assert(pd);
    if (!pd->tags) continue;
    for (idx2 = 0; idx2 < pd->tags->used; idx2++)
    {
      td = (tag_data_t *) array_get_offset(pd->tags, idx2);
      assert(td);
      attrs = (dict_t *) td->attrs;
      var_found = false;
      for (idx3 = 0; idx3 < vars->used; idx3++)
      {
        var = (var_t **) array_get_offset(vars, idx3);
        assert(var);
        if (!var || !(*var)->vname) continue;
        if ( (var_name = lookup(attrs, VAR_NAME)) )
        {
          if (strcmp(var_name, (*var)->vname) == 0)
          {
            var_found = true;
            D("%s: found var: %s", get_string(pd->file_name), (*var)->vname);
            td->var_ptr = *var;
            check_var_args(pd, td, attrs, td->var_ptr->var_args);
            if (td->var_ptr->vcbs.found_cb)
            {
              td->var_ptr->vcbs.found_cb(td);
            }
            break;
          }
        } else {
          E("%s: invalid var at pos: %lu", get_string(pd->file_name), (long unsigned int)td->idx);
        }
      }
      if (!var_found) W("%s: unknown var %s", get_string(pd->file_name), (var_name ? var_name : "..."));
    }
  }
}

void
call_vpreinit_cbs(void)
{
  size_t idx = 0;
  int ret;
  var_t *v = NULL;

  while ( (v = var_foreach(&idx)) )
  {
    if (v->vcbs.preinit_cb)
    {
      DD("preinit builtin %s", v->vname);
      if ( (ret = v->vcbs.preinit_cb()) != 0)
      {
        W("builtin %s returned value %d during preinit", v->vname, ret);
      }
    }
  }
}

void
call_vshutdown_cbs(void)
{
  size_t idx = 0;
  int ret;
  var_t *v = NULL;

  DD2("calling shutdown cbs");
  while ( (v = var_foreach(&idx)) )
  {
    if (v->vcbs.shutdown_cb)
    {
      if ( (ret = v->vcbs.shutdown_cb()) != 0)
      {
        W("builtin %s returned value %d during shutdown", v->vname, ret);
      }
    }
  }
}

void
call_vinit_cbs(void)
{
  size_t idx = 0;
  int ret;
  var_t *v = NULL;

  DD2("init builtins");
  while ( (v = var_foreach(&idx)) )
  {
    if (v->vcbs.init_cb)
    {
      if ( (ret = v->vcbs.init_cb()) != 0)
      {
        W("bultin %s returned value %d during init", v->vname, ret);
      }
    }
  }
}

void
call_vfree_cbs(void)
{
  size_t idx = 0;
  var_t *v = NULL;

  DD2("free builtins");
  while ( (v = var_foreach(&idx)) )
  {
    if (v->vcbs.free_cb)
    {
      v->vcbs.free_cb();
    }
  }
}

